﻿using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using SecretSourceAPI.Constants;
using SecretSourceAPI.Entities;
using SecretSourceAPI.Models;
using SecretSourceAPI.Settings;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace SecretSourceAPI.Services
{
    public class UserService : IUserService
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly RoleManager<IdentityRole> _roleManager;
        private readonly JWT _Jwt;
        public UserService(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<JWT> jwt)
        {
            _userManager = userManager;
            _roleManager = roleManager;
            _Jwt = jwt.Value;
        }

        public async Task<AuthenticationModel> GetTokenAsync(TokenRequestModel model)
        {
            var authenticationModel = new AuthenticationModel();
            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                authenticationModel.IsAuthenticated = false;
                authenticationModel.Message = $"No Accounts Registered with {model.Email}";
                return authenticationModel;
            }
            if (await _userManager.CheckPasswordAsync(user, model.Password))
            {
                authenticationModel.IsAuthenticated = true;
                JwtSecurityToken token =await CreateJwtToken(user);
                authenticationModel.Token=new JwtSecurityTokenHandler().WriteToken(token);
                authenticationModel.Email = user.Email;
                authenticationModel.UserName = user.UserName;
                var roles=await _userManager.GetRolesAsync(user).ConfigureAwait(false);
                authenticationModel.Roles = roles.ToList();

                if (user.RefreshTokens.Any(a => a.IsActive))
                {
                    var activeRefreshToken = user.RefreshTokens.Where(a => a.IsActive).FirstOrDefault();
                    authenticationModel.RefreshToken = activeRefreshToken!.Token;
                    authenticationModel.RefreshTokenExpiration = activeRefreshToken.Expires;
                }
                else
                {
                    var refreshToken = CreateRefreshToken();
                    authenticationModel.RefreshToken=refreshToken.Token;
                    authenticationModel.RefreshTokenExpiration=refreshToken.Expires;
                    user.RefreshTokens.Add(refreshToken);
                    await _userManager.UpdateAsync(user);
                }

                return authenticationModel;
            }
            authenticationModel.IsAuthenticated = false;
            authenticationModel.Message = $"Incorrect Credenticals for user {user.Email}";
            return authenticationModel;
        }

        private async Task<JwtSecurityToken> CreateJwtToken(ApplicationUser user)
        {
            var userClaims = await _userManager.GetClaimsAsync(user);
            var roles = await _userManager.GetRolesAsync(user);

            var roleClaims = new List<Claim>();
            for (int i = 0; i < roles.Count; i++)
            {
                roleClaims.Add(new Claim("roles", roles[i]));
            }
            var claims = new[]
            {
                new Claim(JwtRegisteredClaimNames.Sub,user.UserName),
                new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()),
                new Claim(JwtRegisteredClaimNames.Email,user.Email),
                new Claim("uid",user.Id)
            }.Union(roleClaims)
            .Union(userClaims);

            var symmetricSecurityKey= new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_Jwt.Key));
            var signingCredentials=new SigningCredentials(symmetricSecurityKey,SecurityAlgorithms.HmacSha256);

            var jwtSecurityToken = new JwtSecurityToken(
                issuer: _Jwt.Issuer,
                audience: _Jwt.Audience,
                claims: claims,
                expires: DateTime.UtcNow.AddMinutes(_Jwt.DurationInMinutes),
                signingCredentials:signingCredentials
                );
            return jwtSecurityToken;
        }
        public async Task<string> RegisterUserAsync(RegisterModel model)
        {
            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                var applicationUser = new ApplicationUser()
                {
                    FirstName = model.FirstName,
                    LastName = model.LastName,
                    Email = model.Email,
                    UserName = model.Username
                };
                var result = await _userManager.CreateAsync(applicationUser);
                if (result.Succeeded)
                {
                    await _userManager.AddToRoleAsync(applicationUser, Authorization.default_role.ToString());
                }
                return $"User Registered with username {applicationUser.UserName}";
            }
            else
            {
                return $"Email {model.Email} is already registered.";
            }
        }

        public async Task<string> AddRoleAsync(AddRoleModel model)
        {
            var user=await _userManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                return $"No Accounts Registered with {model.Email}.";
            }
            if (await _userManager.CheckPasswordAsync(user, model.Password))
            {
                var validExits=Enum.GetNames<Authorization.Roles>().Any(x=>x.ToString().ToLower()==model.Role.ToLower());
                if (validExits) { 
                    var validRole=Enum.GetValues<Authorization.Roles>().Cast<Authorization.Roles>().Where(x=>x.ToString()==model.Role.ToLower()).FirstOrDefault();
                    await _userManager.AddToRoleAsync(user, validRole.ToString());
                    return $"Added {model.Role} to user {model.Email}.";
                }
                return $"Role {model.Role} not found";
            }
            return $"Incorrect Credenticals for user {user.Email}";
        }

        private RefreshToken CreateRefreshToken()
        {
            var randomNumber = new byte[32];
            using (var generator = RandomNumberGenerator.Create())
            {
                generator.GetBytes(randomNumber);
                return new RefreshToken()
                {
                    Created = DateTime.UtcNow,
                    Token = Convert.ToBase64String(randomNumber),
                    Expires = DateTime.UtcNow.AddDays(10)
                };
            }
        }

        public async Task<AuthenticationModel> RefreshTokenAsync(string token)
        {
            var authenticationModel=new AuthenticationModel();
            var user =_userManager.Users.SingleOrDefault(x=>x.RefreshTokens.Any(t=>t.Token==token));
            if (user == null)
            {
                authenticationModel.IsAuthenticated = false;
                authenticationModel.Message = $"Token did not match any users.";
                return authenticationModel;
            }

            var refreshToken=user.RefreshTokens.Single(u=>u.Token==token);

            if (!refreshToken.IsActive) {
                authenticationModel.IsAuthenticated = false;
                authenticationModel.Message = $"Token Not Active";
                return authenticationModel;
            }

            //Revoke Current Refresh Token
            refreshToken.Revoked = DateTime.UtcNow;

            //Generate new Refresh Token and save to  Database
            var newRefreshToken = CreateRefreshToken();
            user.RefreshTokens.Add(newRefreshToken);
            await _userManager.UpdateAsync(user);

            //Generates new Jwt
            authenticationModel.IsAuthenticated = true;
            JwtSecurityToken jwtSecurityToken=await CreateJwtToken(user);
            authenticationModel.Token=new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
            authenticationModel.Email = user.Email;
            authenticationModel.UserName = user.UserName;
            var rolesList=await _userManager.GetRolesAsync(user).ConfigureAwait(false);
            authenticationModel.Roles = rolesList.ToList();
            authenticationModel.RefreshToken = newRefreshToken.Token;
            authenticationModel.RefreshTokenExpiration = newRefreshToken.Expires;
            return authenticationModel;
        }

        public async Task<ApplicationUser> GetByIdAsync(string id)
        {
            return await _userManager.FindByIdAsync(id).ConfigureAwait(false);
        }

        public bool RevokeToken(string token)
        {
            var user=_userManager.Users.SingleOrDefault(u=>u.RefreshTokens.Any(t=>t.Token==token));

            //return false if no user found with token
            if (user==null) return false;

            var refreshToken=user.RefreshTokens.Single(x=>x.Token==token);
            //return false if token is not active
            if (!refreshToken.IsActive) return false;

            //revoke token and save
            refreshToken.Revoked = DateTime.UtcNow;
            _userManager.UpdateAsync(user).Wait();
            return true;

        }
    }
}
